package org.six11.olive3.client;

import java.awt.geom.AffineTransform;
import java.awt.geom.RoundRectangle2D;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.JComponent;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.six11.olive3.net.MessageHandler;
import org.six11.olive3.net.MessageNames;
import org.six11.olive3.net.OliveNetworkTransmitter;
import org.six11.olive3.util.Strokes;
import org.six11.util2.Debug;
import org.six11.util2.gui.Colors;

/**
 * 
 **/
public class OliveDrawingSurface extends JComponent {

  // border and background UI variables
  private Color bgColor;
  private Color penEnabledBorderColor;
  private Color penDisabledBorderColor;
  private double borderPad;
  private OliveNetworkTransmitter xmit;
  private OliveMouseThing mouseThing;
  private ForeignPenHandler foreignPenHandler;
  private boolean penEnabled = false;

  private double xScale, yScale;

  // storage for all raw strokes
  private OliveState state;

  public OliveState getState() {
    return state;
  }

  // renderer
  private StrokeRenderer renderer;

  /**
   * Make an Olive3 object, but do not show it.
   */
  public OliveDrawingSurface(OliveNetworkTransmitter xmit, OliveState state) {
    this.xmit = xmit;
    this.state = state;
    xScale = 1.0;
    yScale = 1.0;
    try {
      initGUI();
    } catch (MalformedURLException ex) {
      ex.printStackTrace();
    }
  }

  private final void initGUI() throws MalformedURLException {
    // establish border and background variables
    bgColor = Colors.getDefault().get(Colors.BACKGROUND);
    penEnabledBorderColor = Colors.getDefault().get(Colors.ACCENT);
    penDisabledBorderColor = Colors.getDefault().get(Colors.SELECTED_BG_INACTIVE);
    borderPad = 3.0;

    // connect state to mouse events to record raw user input
    state.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        repaint();
      }
    });

    // establish a default renderer
    renderer = new BasicRenderer();

    // make a stroke listener that listens for pen data from the server
    // xmit.getStreamHandler().registerAsynchronousHandler(MessageNames.MSG_PEN,
    // new ForeignPenHandler(state));

    // establish mouse/pen input handler
    mouseThing = new OliveMouseThing(state, xmit);
    setPenEnabled(true);
  }

  /**
   * Makes a pen handler that is suitable to hook it up to the network receiver. If the network
   * receiver gets 'pen' messages, it will use this drawing surface to display them.
   */
  public ForeignPenHandler getForeignPenHandler() {
    if (foreignPenHandler == null) {
      foreignPenHandler = new ForeignPenHandler(state);
    }
    return foreignPenHandler;
  }

  public void setPenEnabled(boolean v) {
    if (v) {
      addMouseListener(mouseThing);
      addMouseMotionListener(mouseThing);
    } else {
      removeMouseListener(mouseThing);
      removeMouseMotionListener(mouseThing);
    }
    penEnabled = v;
    repaint();
  }

  public double getXScale() {
    return xScale;
  }

  public void setXScale(double scale) {
    if (scale > 1.0 || scale < 0.0) {
      throw new IllegalArgumentException("X scale must be in the range (0..1)");
    }
    xScale = scale;
  }

  public double getYScale() {
    return yScale;
  }

  public void setYScale(double scale) {
    if (scale > 1.0 || scale < 0.0) {
      throw new IllegalArgumentException("Y scale must be in the range (0..1)");
    }
    yScale = scale;
  }

  public void paintComponent(Graphics g1) {
    Graphics2D g = (Graphics2D) g1;
    drawBorderAndBackground(g);
    AffineTransform xform = g.getTransform();
    g.scale(getXScale(), getYScale());
    synchronized (state.getSequences()) {
      renderer.renderSequences(g, state.getSequences());
    }
    g.setTransform(xform);
  }

  private void bug(String what) {
    Debug.out("OliveDrawingSurface", what);
  }

  protected void drawBorderAndBackground(Graphics2D g) {
    g.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON));
    RoundRectangle2D rec = new RoundRectangle2D.Double(borderPad, borderPad, getWidth() - 2.0
        * borderPad, getHeight() - 2.0 * borderPad, 40, 40);
    g.setColor(bgColor);
    g.fill(rec);
    g.setStroke(Strokes.DASHED_BORDER_STROKE);
    g.setColor(penEnabled ? penEnabledBorderColor : penDisabledBorderColor);
    g.draw(rec);
  }
}
